package com.authsphere.security.token.redis;

import com.authsphere.security.token.common.AccessToken;
import com.authsphere.security.token.common.AuthenticationTokenServices;
import com.authsphere.security.token.common.TokenStore;
import com.authsphere.security.token.redis.configuration.AccessTokenConfig;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
import org.springframework.security.crypto.keygen.KeyGenerators;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.Instant;
import java.util.Base64;
import java.util.Date;

/**
 * @program: AuthSphere
 * @description:
 * @author: YuKai Fan
 * @create: 2025/3/12 10:36
 **/
public class RedisTokenServices implements AuthenticationTokenServices {
    private static final BytesKeyGenerator DEFAULT_TOKEN_GENERATOR = KeyGenerators.secureRandom(20);
    private static final Charset US_ASCII = StandardCharsets.US_ASCII;
    private TokenStore tokenStore;

    private final AccessTokenConfig accessTokenConfig;

    private final SecureRandom random;

    public RedisTokenServices(AccessTokenConfig accessTokenConfig) {
        this.random = new SecureRandom();
        this.accessTokenConfig = accessTokenConfig;
    }

    @Override
    public AccessToken createAccessToken(Authentication authentication, boolean force) throws AuthenticationException {
        if (!force) {
            AccessToken accessToken = tokenStore.getAccessToken(authentication);
            if (accessToken != null) {
                if (accessToken.isExpired()) {
                    tokenStore.removeAccessToken(accessToken);
                } else {
                    tokenStore.storeAccessToken(accessToken, authentication);
                    return accessToken;
                }
            }
        }
        AccessToken newAccessToken = createNewAccessToken(authentication);
        tokenStore.storeAccessToken(newAccessToken, authentication);
        return newAccessToken;
    }

    @Override
    public AccessToken refreshAccessToken(AccessToken accessToken, Authentication authentication) throws AuthenticationException {
        AccessToken newAccessToken = createNewAccessToken(accessToken);
        tokenStore.storeAccessToken(newAccessToken, authentication);
        return newAccessToken;
    }

    @Override
    public AccessToken getAccessToken(Authentication authentication) {
        return tokenStore.getAccessToken(authentication);
    }

    @Override
    public AccessToken getAccessToken(String tokenValue) {
        return tokenStore.readAccessToken(tokenValue);
    }

    @Override
    public Authentication getAuthentication(String tokenValue) {
        return tokenStore.readAuthentication(tokenValue);
    }

    @Override
    public void removeAccessToken(String tokenValue) {
        tokenStore.removeAccessToken(tokenValue);
    }

    public TokenStore getTokenStore() {
        return tokenStore;
    }

    public void setTokenStore(TokenStore tokenStore) {
        this.tokenStore = tokenStore;
    }

    private AccessToken createNewAccessToken(Authentication authentication) {
        String tokenValue = new String(Base64.getEncoder().encode(DEFAULT_TOKEN_GENERATOR.generateKey()),  US_ASCII);
        ExpirationAccessToken token = new ExpirationAccessToken(tokenValue);
        token.setSeriesId(generateSeriesData());
        int validitySeconds = accessTokenConfig.getAccessTokenValiditySeconds();
        if (validitySeconds > 0) {
            token.setExpiration(Instant.now().plusSeconds(validitySeconds));
        }
        return token;
    }

    private AccessToken createNewAccessToken(AccessToken accessToken) {
        ExpirationAccessToken token = new ExpirationAccessToken(accessToken);
        int validitySeconds = accessTokenConfig.getAccessTokenValiditySeconds();
        if (validitySeconds > 0) {
            token.setExpiration(Instant.now().plusSeconds(validitySeconds));
        }
        return token;
    }


    protected String generateSeriesData() {
        byte[] newSeries = new byte[accessTokenConfig.getSeriesLength()];
        this.random.nextBytes(newSeries);
        return new String(Base64.getEncoder().encode(newSeries));
    }

}
